package io.github.echarts.model.geometry;

/**
 * 表示二维平面上的线段
 * 使用逻辑坐标系（左下角为原点，Y轴向上为正方向）
 */
public class Line {
    
    /**
     * 线段起点
     */
    private final Point start;
    
    /**
     * 线段终点
     */
    private final Point end;
    
    /**
     * 创建线段
     * 
     * @param start 起点
     * @param end 终点
     */
    public Line(Point start, Point end) {
        if (start == null || end == null) {
            throw new IllegalArgumentException("线段的起点和终点不能为null");
        }
        this.start = start;
        this.end = end;
    }
    
    /**
     * 创建线段
     * 
     * @param x1 起点X坐标
     * @param y1 起点Y坐标
     * @param x2 终点X坐标
     * @param y2 终点Y坐标
     */
    public Line(double x1, double y1, double x2, double y2) {
        this(new Point(x1, y1), new Point(x2, y2));
    }
    
    /**
     * 获取线段起点
     * 
     * @return 起点
     */
    public Point getStart() {
        return start;
    }
    
    /**
     * 获取线段终点
     * 
     * @return 终点
     */
    public Point getEnd() {
        return end;
    }
    
    /**
     * 获取线段长度
     * 
     * @return 线段长度
     */
    public double getLength() {
        return start.distanceTo(end);
    }
    
    /**
     * 获取线段的中点
     * 
     * @return 线段中点
     */
    public Point getMidPoint() {
        return start.midPoint(end);
    }
    
    /**
     * 计算线段的斜率
     * 
     * @return 线段斜率，如果是垂直线段则返回Double.POSITIVE_INFINITY
     */
    public double getSlope() {
        double dx = end.getX() - start.getX();
        double dy = end.getY() - start.getY();
        
        if (Math.abs(dx) < 1e-10) {
            return Double.POSITIVE_INFINITY; // 垂直线
        }
        
        return dy / dx;
    }
    
    /**
     * 判断点是否在线段上（考虑一定的误差）
     * 
     * @param point 要检查的点
     * @param epsilon 允许的误差，默认为1e-10
     * @return 如果点在线段上，返回true，否则返回false
     */
    public boolean contains(Point point, double epsilon) {
        // 首先检查点是否在线段的矩形范围内
        double minX = Math.min(start.getX(), end.getX());
        double maxX = Math.max(start.getX(), end.getX());
        double minY = Math.min(start.getY(), end.getY());
        double maxY = Math.max(start.getY(), end.getY());
        
        if (point.getX() < minX - epsilon || point.getX() > maxX + epsilon ||
            point.getY() < minY - epsilon || point.getY() > maxY + epsilon) {
            return false;
        }
        
        // 如果是垂直线段
        if (Math.abs(start.getX() - end.getX()) < epsilon) {
            return Math.abs(point.getX() - start.getX()) < epsilon;
        }
        
        // 如果是水平线段
        if (Math.abs(start.getY() - end.getY()) < epsilon) {
            return Math.abs(point.getY() - start.getY()) < epsilon;
        }
        
        // 一般情况：计算点到线段的距离，如果足够小则认为在线段上
        return distanceTo(point) < epsilon;
    }
    
    /**
     * 判断点是否在线段上（使用默认误差1e-10）
     * 
     * @param point 要检查的点
     * @return 如果点在线段上，返回true，否则返回false
     */
    public boolean contains(Point point) {
        return contains(point, 1e-10);
    }
    
    /**
     * 计算点到线段的最短距离
     * 
     * @param point 目标点
     * @return 点到线段的最短距离
     */
    public double distanceTo(Point point) {
        // 线段向量
        double lineVectorX = end.getX() - start.getX();
        double lineVectorY = end.getY() - start.getY();
        
        // 线段长度的平方
        double lineLengthSquared = lineVectorX * lineVectorX + lineVectorY * lineVectorY;
        
        // 如果线段长度为0（即起点等于终点），则返回点到起点的距离
        if (lineLengthSquared < 1e-10) {
            return point.distanceTo(start);
        }
        
        // 计算投影比例 t，表示投影点在线段上的位置 (0 <= t <= 1)
        double pointVectorX = point.getX() - start.getX();
        double pointVectorY = point.getY() - start.getY();
        double t = (pointVectorX * lineVectorX + pointVectorY * lineVectorY) / lineLengthSquared;
        
        if (t < 0) {
            // 投影点在线段起点之前，最短距离是到起点的距离
            return point.distanceTo(start);
        }
        
        if (t > 1) {
            // 投影点在线段终点之后，最短距离是到终点的距离
            return point.distanceTo(end);
        }
        
        // 投影点在线段上，计算投影点坐标
        double projectionX = start.getX() + t * lineVectorX;
        double projectionY = start.getY() + t * lineVectorY;
        Point projection = new Point(projectionX, projectionY);
        
        // 返回点到投影点的距离
        return point.distanceTo(projection);
    }
    
    /**
     * 判断此线段是否与另一个线段相交
     * 
     * @param other 另一个线段
     * @return 如果两线段相交，返回true，否则返回false
     */
    public boolean intersects(Line other) {
        // 使用叉积判断两个线段是否相交
        double x1 = this.start.getX();
        double y1 = this.start.getY();
        double x2 = this.end.getX();
        double y2 = this.end.getY();
        
        double x3 = other.start.getX();
        double y3 = other.start.getY();
        double x4 = other.end.getX();
        double y4 = other.end.getY();
        
        // 计算叉积
        double d1 = crossProduct(x3, y3, x4, y4, x1, y1);
        double d2 = crossProduct(x3, y3, x4, y4, x2, y2);
        double d3 = crossProduct(x1, y1, x2, y2, x3, y3);
        double d4 = crossProduct(x1, y1, x2, y2, x4, y4);
        
        // 如果d1和d2的符号相反，且d3和d4的符号也相反，则两条线段相交
        return ((d1 * d2 < 0) && (d3 * d4 < 0));
    }
    
    /**
     * 计算两个线段的交点
     * 
     * @param other 另一个线段
     * @return 交点，如果不相交则返回null
     */
    public Point getIntersectionPoint(Line other) {
        if (!intersects(other)) {
            return null;
        }
        
        double x1 = this.start.getX();
        double y1 = this.start.getY();
        double x2 = this.end.getX();
        double y2 = this.end.getY();
        
        double x3 = other.start.getX();
        double y3 = other.start.getY();
        double x4 = other.end.getX();
        double y4 = other.end.getY();
        
        // 线段表示为参数方程：P = P1 + t(P2 - P1)
        // 求解两个参数方程的交点
        double denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
        
        // 如果分母为0，则两线段平行或共线
        if (Math.abs(denominator) < 1e-10) {
            return null;
        }
        
        double ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator;
        double ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator;
        
        // 检查交点是否在两个线段内
        if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
            double x = x1 + ua * (x2 - x1);
            double y = y1 + ua * (y2 - y1);
            return new Point(x, y);
        }
        
        return null;
    }
    
    /**
     * 创建一个新的线段，表示当前线段偏移指定距离后的结果
     * 
     * @param deltaX X轴偏移量
     * @param deltaY Y轴偏移量
     * @return 偏移后的新线段
     */
    public Line offset(double deltaX, double deltaY) {
        Point newStart = new Point(start.getX() + deltaX, start.getY() + deltaY);
        Point newEnd = new Point(end.getX() + deltaX, end.getY() + deltaY);
        return new Line(newStart, newEnd);
    }
    
    /**
     * 计算线段的角度（与X轴正方向的夹角，单位为弧度）
     * 
     * @return 线段角度（0到2π）
     */
    public double getAngle() {
        double dx = end.getX() - start.getX();
        double dy = end.getY() - start.getY();
        double angle = Math.atan2(dy, dx);
        
        // 确保角度在[0, 2π)范围内
        if (angle < 0) {
            angle += 2 * Math.PI;
        }
        
        return angle;
    }
    
    /**
     * 计算线段方向向量
     * 
     * @return 方向向量，长度为1
     */
    public Point getDirectionVector() {
        double dx = end.getX() - start.getX();
        double dy = end.getY() - start.getY();
        double length = Math.sqrt(dx * dx + dy * dy);
        
        if (length < 1e-10) {
            return new Point(0, 0); // 如果线段长度接近0，返回零向量
        }
        
        return new Point(dx / length, dy / length);
    }
    
    /**
     * 计算叉积(p2-p1) × (p3-p1)，用于判断点p3在向量p1p2的左侧还是右侧
     */
    private double crossProduct(double x1, double y1, double x2, double y2, double x3, double y3) {
        return (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1);
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        
        Line line = (Line) o;
        
        if (!start.equals(line.start)) return false;
        return end.equals(line.end);
    }
    
    @Override
    public int hashCode() {
        int result = start.hashCode();
        result = 31 * result + end.hashCode();
        return result;
    }
    
    @Override
    public String toString() {
        return "Line{" +
                "start=" + start +
                ", end=" + end +
                '}';
    }
} 